Add :is(), retain argument selectors for :not()/:has()/:matches(), fix forgiving-list specificity - #201
Merged
TylerBrinks merged 1 commit intoJul 23, 2026
Conversation
…x forgiving-list specificity :not(), :has() and :matches() collapsed their parsed argument to a formatted string and wrapped it in an opaque PseudoClassSelector, so the inner selector object - and any way to inspect or re-match it - was lost. Introduce NotSelector, HasSelector and MatchesSelector, each retaining the parsed argument ISelector, and add :is() as the modern alias of :matches() (they share MatchesSelector; a Keyword field controls which form it serializes to). Fix specificity to match CSS Selectors 4 16.1: the specificity of an :is()/:not()/:has() is that of the most specific complex selector in its argument. That requires a selector list to report the static max of its alternatives rather than their sum, so Selectors.Specificity becomes virtual and ListSelector overrides it. CompoundSelector keeps the sum, which is correct - its members all constrain one element. Verified against the spec's own examples: :is(em, #foo) is (1,0,0) and :not(em, strong#foo) is (1,0,1).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
:not(),:has()and:matches()throw away their parsed argument. Each function state formats the inner selector back to a string and wraps it in an opaquePseudoClassSelector:So after parsing
a:not(.foo)there is no way to reach the.fooselector object — only its round-tripped text survives. And:is(), the current name for:matches()(CSS Selectors 4 §4.2), isn't recognized at all.Changes
NotSelector/HasSelector/MatchesSelector— each retains the parsed argumentISelector(exposed asInner) and round-trips correctly.:is()as the modern alias of:matches(). Both shareMatchesSelector; aKeywordfield controls whether it serializes as:is(...)or:matches(...).Forgiving-list specificity per CSS Selectors 4 §16.1:
A selector list must therefore report the max of its alternatives, not their sum.
Selectors.SpecificitybecomesvirtualandListSelectoroverrides it to the max.CompoundSelectorkeeps the sum — correct, since its members all constrain one element.Verified against the spec's own examples
:is(em, #foo)(1,0,0):not(em, strong#foo)(1,0,1)Tests
11 tests in
SelectorObjectModelTests.cs: each function produces its typed selector and retainsInner;:is()/:matches()round-trip to their own keyword; the two spec examples plusa:not(.foo)/a:has(.foo)/a:is(.foo, #bar)specificities; and that a list's specificity is the max while a compound's stays the sum.The full suite (1263 existing tests) stays green — the specificity change regresses nothing — and all seven target frameworks build with no new warnings. The new selector types are additive; the only behavioural change to an existing type is
ListSelector.Specificity(sum → max), which is the spec fix above.